// Copyright (C) ZHENG Zhong <heavyzheng nospam-at gmail D0T com>
// http://www.zhengzhong.net/
//
// $Id: parser.cpp 318 2009-03-02 14:03:21Z heavyzheng $
//

#ifndef ZZHENG_XML_SOURCE
#define ZZHENG_XML_SOURCE
#endif // ZZHENG_XML_SOURCE

#include <zzheng/xml/sax/libxml2/parser.hpp>
#include <zzheng/xml/sax/attribute.hpp>
#include <zzheng/xml/sax/error_info.hpp>
#include <zzheng/xml/sax/features.hpp>

#include <cassert>
#include <cstring>
#include <map>
#include <string>
#include <utility>
#include <vector>

#include <libxml/parser.h>
#include <libxml/xmlerror.h>


namespace zzheng {
namespace xml {
namespace sax {
namespace libxml2_ {

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // helper functions in anonymous namespace
    //

    namespace {

        //! Converts libxml2's xmlChar* to const char*.
        const char* to_chars_(const xmlChar* chars) {
            return reinterpret_cast<const char*>(chars);
        }

        //! Casts the user data pointer to a pointer to SaxParserLibxml2Impl.
        //! \param user_data  pointer to user-defined data (should be a pointer to a SAX parser).
        //! \return pointer to the SAX parser.
        parser* get_parser_(void* user_data) {
            assert(user_data != 0 && "User-defined data should not be null.");
            return static_cast<parser*>(user_data);
        }

        std::pair<std::string, std::string> split_qname_(const std::string& qname) {
            std::string::size_type index = qname.find_first_of(":");
            if (index == std::string::npos) {
                return std::make_pair(std::string(), qname);
            } else {
                return std::make_pair(qname.substr(0, index), qname.substr(index + 1));
            }
        }

        std::string extract_ns_prefix_(const std::string& qname) {
            std::string::size_type index = qname.find_first_of(":");
            if (index == std::string::npos) {
                return std::string();
            } else {
                return qname.substr(0, index);
            }
        }

        std::string extract_local_name_(const std::string& qname) {
            std::string::size_type index = qname.find_first_of(":");
            if (index == std::string::npos) {
                return qname;
            } else {
                return qname.substr(index + 1);
            }
        }

        //! Checks if the attribute is a default namespace declaration. An attribute like
        //! "xmlns='...' declares a default namespace.
        //! \param prefix  the attribute's prefix.
        //! \param name    the attribute's local name.
        //! \return true if this attribute declares a default namespace, false otherwise.
        bool is_default_ns_(const std::string& prefix, const std::string& name) {
            return (prefix.empty() && name == "xmlns");
        }

        //! Checks if the attribute is a namespace declaration. An attribute like "xmlns:X='...'
        //! declares a namespace with prefix "X".
        //! \param prefix  the attribute's prefix.
        //! \param name    the attribute's local name.
        //! \return true if this attribute declares a namespace, false otherwise.
        bool is_ns_(const std::string& prefix, const std::string& name) {
            return (prefix == "xmlns" && !name.empty());
        }

        //! Creates a Libxml2 SAX handler with all the callback functions registered.
        //! See: http://www.xmlsoft.org/html/libxml-tree.html#xmlSAXHandler
        xmlSAXHandler create_libxml2_sax_handler_() {

            xmlSAXHandler handler;

            // Initialize the callback function pointers for content handler.
            handler.startDocument = &begin_doc;
            handler.endDocument = &end_doc;
            handler.startElementNs = 0;
            handler.endElementNs = 0;
            handler.startElement = &begin_elem;
            handler.endElement = &end_elem;
            handler.characters = &characters;
            handler.cdataBlock = &cdata_block;
            handler.ignorableWhitespace = &ignorable_whitespace;
            handler.comment = &comment;

            // Initialize the callback function pointers for error handler.
            handler.warning = 0;
            handler.error = 0;
            handler.fatalError = 0;
            handler.serror = &structured_error;

            handler.internalSubset = 0;
            handler.isStandalone = 0;
            handler.hasInternalSubset = 0;
            handler.hasExternalSubset = 0;
            handler.resolveEntity = 0;
            handler.getEntity = 0;
            handler.entityDecl = 0;
            handler.notationDecl = 0;
            handler.attributeDecl = 0;
            handler.elementDecl = 0;
            handler.unparsedEntityDecl = 0;
            handler.setDocumentLocator = 0;
            handler.reference = 0;

            handler.processingInstruction = 0;
            handler.getParameterEntity = 0;

            handler.externalSubset = 0;
            handler._private = 0;
            handler.initialized = XML_SAX2_MAGIC; // Use Libxml2 SAX2!

            return handler;
        }

    } // anonymous namespace


    ////////////////////////////////////////////////////////////////////////////////////////////////
    // libxml2 SAX callbacks
    //

    void begin_doc(void* user_data) {
        get_parser_(user_data)->begin_doc();
    }
    
    void end_doc(void* user_data) {
        get_parser_(user_data)->end_doc();
    }

    void begin_elem(void* user_data, const xmlChar* qname, const xmlChar** attrs) {

        // Get the SAX parser from user-defined data.
        parser* p = get_parser_(user_data);

        // Firstly, handle the attributes associated with this element.
        // Note: this step may invoke the begin_ns() callback.
        attribute_list sax_attrs;
        if (attrs != 0) {
            for (unsigned int i = 0; attrs[i] != 0; i = i + 2) {
                // Extract the attribute's qname, prefix, local name and value.
                std::string qname = to_chars_(attrs[i]);
                std::pair<std::string, std::string> prefix_name = split_qname_(qname);
                std::string value;
                if (attrs[i + 1] != 0) {
                    value = to_chars_(attrs[i + 1]);
                }
                // If the attribute is a namespace declaration (declaring the default namespace or
                // declaring a namespace prefix-URI mapping), associate it to the current element
                // and notify the parser.
                bool is_ns = false;
                if (is_default_ns_(prefix_name.first, prefix_name.second)) {
                    is_ns = true;
                    p->begin_ns(std::string(), value);
                } else if (is_ns_(prefix_name.first, prefix_name.second)) {
                    is_ns = true;
                    p->begin_ns(prefix_name.second, value);
                }
                // Add attribute to the SAX attribute list if necessary:
                // 1) this attribute is NOT a namespace declaration, OR
                // 2) the parser's namespace-prefixes feature is enabled.
                if (!is_ns || p->is_ns_prefixes_enabled()) {
                    std::string uri = p->find_uri(prefix_name.first);
                    attribute sax_attr(prefix_name.second, uri, prefix_name.first, value);
                    sax_attrs.push_back(sax_attr);
                }
            }
        }

        // Secondly, handle the beginning of the element.
        // Note: this step will invoke the begin_elem() callback.
        std::pair<std::string, std::string> prefix_name = split_qname_(to_chars_(qname));
        std::string uri = p->find_uri(prefix_name.first);
        p->begin_elem(prefix_name.second, uri, to_chars_(qname), sax_attrs);
    }

    void end_elem(void* user_data, const xmlChar* qname) {

        parser* p = get_parser_(user_data);

        // Firstly, handle the end of the element.
        // Note: this step will invoke the end_elem() callback.
        std::pair<std::string, std::string> prefix_name = split_qname_(to_chars_(qname));
        std::string uri = p->find_uri(prefix_name.first);
        p->end_elem(prefix_name.second, uri, to_chars_(qname));

        // Secondly, if one or more namespaces are out of scope, handle them (in reversed order,
        // via a reverse iterator).
        // Note: this step will invoke the end_ns() callback.
        const std::vector<std::string>& prefixes = p->get_deprecated_prefixes();
        typedef std::vector<std::string>::const_reverse_iterator const_reverse_iterator;
        for (const_reverse_iterator i = prefixes.rbegin(); i != prefixes.rend(); ++i) {
            p->end_ns(*i);
        }
    }

    void characters(void* user_data, const xmlChar* chars, int length) {
        get_parser_(user_data)->characters(to_chars_(chars), 0, length);
    }

    void cdata_block(void* user_data, const xmlChar* cdata, int length) {
        get_parser_(user_data)->characters(to_chars_(cdata), 0, length);
    }

    void ignorable_whitespace(void* user_data, const xmlChar* spaces, int length) {
        get_parser_(user_data)->ignorable_whitespace(to_chars_(spaces), 0, length);
    }

    void comment(void* user_data, const xmlChar* comment) {
        get_parser_(user_data)->comment(to_chars_(comment), 0, xmlStrlen(comment));
    }

    void structured_error(void* user_data, xmlError* err) {
        error_info info(err->message, err->file, err->line, 0);
        switch (err->level) {
        case XML_ERR_WARNING:
            get_parser_(user_data)->warning(info);
            break;
        case XML_ERR_ERROR:
            get_parser_(user_data)->error(info);
            break;
        case XML_ERR_FATAL:
            get_parser_(user_data)->fatal(info);
            break;
        default:
            // We should not arrive here.
            break;
        }
        
    }


    ////////////////////////////////////////////////////////////////////////////////////////////////
    // parser :: constructor and destructor
    //

    parser::parser()
    : stack_(), popped_(), namespaces_(), features_(), content_handler_(0), error_handler_(0) {
        features_[namespaces_feature] = true;
        features_[namespace_prefixes_feature] = false;
    }

    parser::~parser() {
        content_handler_ = 0;
        error_handler_ = 0;
    }


    ////////////////////////////////////////////////////////////////////////////////////////////////
    // parser :: public member functions
    //

    void parser::set_content_handler(content_handler* handler) {
        content_handler_ = handler;
    }

    void parser::set_error_handler(error_handler* handler) {
        error_handler_ = handler;
    }

    void parser::set_feature(const std::string& name, bool enable) {
        features_[name] = enable;
    }

    bool parser::get_feature(const std::string& name) const {
        feature_map::const_iterator i = features_.find(name);
        if (i != features_.end()) {
            return i->second;
        } else {
            return false;
        }
    }

    bool parser::is_ns_enabled() const {
        return get_feature(namespaces_feature);
    }

    bool parser::is_ns_prefixes_enabled() const {
        return get_feature(namespace_prefixes_feature);
    }

    void parser::parse_file(const std::string& file_name) {
        xmlSAXHandler handler = create_libxml2_sax_handler_();
        int zero_is_ok = xmlSAXUserParseFile( &handler,            // pointer to SAX handler.
                                              this,                // pointer to user data.
                                              file_name.c_str() ); // file name.

        if (zero_is_ok == 0) {
            // TODO: success!
        } else {
            // TODO: error!
        }
    }

    void parser::parse_string(const char* str) {
        xmlSAXHandler handler = create_libxml2_sax_handler_();
        int zero_is_ok = xmlSAXUserParseMemory( &handler,           // pointer to SAX handler.
                                                this,               // pointer to user data.
                                                str,                // buffer holding the XML.
                                                std::strlen(str) ); // buffer size.
        if (zero_is_ok == 0) {
            // TODO: success!
        } else {
            // TODO: error!
        }
    }


    ////////////////////////////////////////////////////////////////////////////////////////////////
    // parser :: private callback member functions
    //

    void parser::begin_doc() {
        // Push the first empty element to element stack.
        stack_.clear();
        stack_.push_back(element());
        // Invoke the content handler's callback function.
        if (content_handler_ != 0) {
            content_handler_->begin_doc();
        }
    }

    void parser::end_doc() {
        // Invoke the content handler's callback function.
        if (content_handler_ != 0) {
            content_handler_->end_doc();
        }
        // Clear the element stack. No assertions: errors may have occurred.
        stack_.clear();
    }

    void parser::begin_ns(const std::string& prefix, const std::string& uri) {
        // Register the namespace's prefix-URI and invoke the content handler's callback function.
        namespaces_[prefix].push_back(uri);
        if (content_handler_ != 0) {
            content_handler_->begin_ns(prefix, uri);
        }
        // Add the start prefix to the top element on the element stack.
        // Assertion: the element stack should not be not empty.
        assert(!stack_.empty());
        stack_.back().prefixes_.push_back(prefix);
    }

    void parser::end_ns(const std::string& prefix) {
        // Invoke the content handler's callback function.
        if (content_handler_ != 0) {
            content_handler_->end_ns(prefix);
        }
        // Unregister the namespace prefix-URI mapping.
        // Assertion: the deprecated prefix must exist in the prefix mappings.
        prefix_uris_map::iterator i = namespaces_.find(prefix);
        assert(i != namespaces_.end() && !i->second.empty());
        i->second.pop_back();
        if (i->second.empty()) {
            namespaces_.erase(i);
        }
    }

    void parser::begin_elem(const std::string& name,
                            const std::string& uri,
                            const std::string& qname,
                            const attribute_list& attrs) {
        // Invoke the content handler's callback function.
        if (content_handler_ != 0) {
            content_handler_->begin_elem(name, uri, qname, attrs);
        }
        // Set the element's qname to the top element on the element stack, and push it.
        // Assertion: before pushing the element to the stack, the stack is not empty.
        assert(!stack_.empty());
        stack_.back().qname_ = qname;
        stack_.push_back(element());
    }

    void parser::end_elem(const std::string& name,
                          const std::string& uri,
                          const std::string& qname) {
        // Invoke the content handler's callback function.
        if (content_handler_ != 0) {
            content_handler_->end_elem(name, uri, qname);
        }
        // Pop out the end element from the stack, copy it to the popped element, and clear it.
        // Assertions:
        // 1) before popping out the element from the stack, the stack is not empty.
        // 2) after popping out the element from the stack, the stack is not empty.
        // 3) after popping out the element, the top element's QName is consistent.
        assert(!stack_.empty());
        stack_.pop_back();
        assert(!stack_.empty() && stack_.back().qname_ == qname);
        popped_ = stack_.back();
        stack_.back().qname_ = std::string();
        stack_.back().prefixes_.clear();
    }

    void parser::characters(const char* chars, size_type start, size_type length) {
        if (content_handler_ != 0) {
            content_handler_->characters(chars, start, length);
        }
    }

    void parser::ignorable_whitespace(const char* chars, size_type start, size_type length) {
        if (content_handler_ != 0) {
            content_handler_->ignorable_whitespace(chars, start, length);
        }
    }

    void parser::comment(const char* chars, size_type start, size_type length) {
        if (content_handler_ != 0) {
            content_handler_->comment(chars, start, length);
        }
    }

    void parser::warning(const error_info& info) {
        if (error_handler_ != 0) {
            error_handler_->warning(info);
        }
    }

    void parser::error(const error_info& info) {
        if (error_handler_ != 0) {
            error_handler_->error(info);
        }
    }

    void parser::fatal(const error_info& info) {
        if (error_handler_ != 0) {
            error_handler_->fatal(info);
        }
    }


    ////////////////////////////////////////////////////////////////////////////////////////////////
    // parser :: private utility member functions
    //

    std::string parser::find_uri(const std::string& prefix) const {
        prefix_uris_map::const_iterator i = namespaces_.find(prefix);
        if (i == namespaces_.end()) {
            return std::string();
        } else {
            return i->second.back();
        }
    }

    const std::vector<std::string>& parser::get_deprecated_prefixes() const {
        return popped_.prefixes_;
    }


} // namespace zzheng::xml::sax::libxml2_
} // namespace zzheng::xml::sax
} // namespace zzheng::xml
} // namespace zzheng





